﻿using System;
using NUnit.Core;
using SeleniumToolkit.Config;
using SeleniumToolkit.Core;
using NUnit.Core.Extensibility;
using System.Collections;
using System.Reflection;

namespace SeleniumToolkit.NUnit
{
    /// <summary>
    /// Constructs WebTestCase objects during TestPackage Load
    /// </summary>
    public class SeleniumTestFixtureBuilder : ISuiteBuilder
    {
        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <param name="host">NUnit extension host</param>
        /// <param name="config">Selenium Configuration contains information about how the selenium objects are constructed</param>
        public SeleniumTestFixtureBuilder(IExtensionHost host, SeleniumConfiguration config)
        {
            _host = host;
            _config = config;
        }

        /// <summary>
        /// Name of the Builder
        /// </summary>
        public string Name;

        #region ISuiteBuilder Members

        /// <summary>
        /// Determines if a type should be considered a WebFixture
        /// </summary>
        /// <param name="type"></param>
        /// <returns>Returns true when a valid candidate type</returns>
        public bool CanBuildFrom(Type type)
        {
            // TODO: allow types that don't have webfixtureattribute?
            bool result = (!type.IsAbstract) &&
                          Reflect.HasAttribute(type, "SeleniumToolkit.WebFixtureAttribute", false);

            return result;
        }

        /// <summary>
        /// Constructs the Fixture and contained tests
        /// </summary>
        /// <param name="type"></param>
        /// <returns>Returns a fully constructed Suite</returns>
        public Test BuildFrom(Type type)
        {
            var fixture = new SeleniumTestFixture(type);

            ApplySettingsToFixture(type, fixture);
            CheckIfFixtureIsValid(fixture);
            AddTestCases(type, fixture);
            NUnitFramework.ApplyCommonAttributes(type, fixture);

            SeleniumEventListener.WebTestsPresent = true;

            return fixture;
        }

        #endregion

        #region private instance members


        /// <summary>
        /// Locate and add all test cases in this fixture
        /// </summary>
        /// <param name="fixtureType"></param>
        /// <param name="suite"></param>
        private void AddTestCases(Type fixtureType, TestSuite suite)
        {
            // Get Methods
            IList methods = fixtureType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);

            // Create tests
            foreach (MethodInfo method in methods)
            {
                Test test = BuildTestCase(method,suite);
                if (test != null)
                {
                    suite.Add(test);
                }
            }
        }

        /// <summary>
        /// Copy configuration settings from attributes and configuration
        /// </summary>
        /// <param name="fixtureType"></param>
        /// <param name="fixture"></param>
        private void ApplySettingsToFixture(Type fixtureType, SeleniumTestFixture fixture)
        {
            // copy settings from config
            fixture.BaseUrl = _config.BrowserUrl;
            fixture.DefaultBrowser = _config.DefaultBrowser;
            fixture.Timeout = _config.Timeout.ToString();
            fixture.SessionOwner = _config.AddinSettings.RecylePerFixture;

            // copy settings from class
            SessionInfoHelper.CopyAttributes(fixture, fixtureType);
        }

        /// <summary>
        /// Construct a test case
        /// </summary>
        /// <param name="method"></param>
        /// <param name="suite"></param>
        /// <returns></returns>
        protected Test BuildTestCase(MethodInfo method, Test suite)
        {
            ITestCaseBuilder2 testCaseBuilders = GetTestBuilders();

            Test test = null;

            // use the builder to construct our testcase
            if (testCaseBuilders.CanBuildFrom(method, suite))
            {
                test = testCaseBuilders.BuildFrom(method, suite);
            }

            // use the test decorator extensions to decorate our test
            if (test != null)
            {
                ITestDecorator testDecorators = GetTestDecorators();
                testDecorators.Decorate(test, method);
            }

            return test;
        }

        /// <summary>
        /// Validate that the fixture
        /// </summary>
        /// <param name="fixture"></param>
        private static void CheckIfFixtureIsValid(TestFixture fixture)
        {
            Type fixtureType = fixture.FixtureType;
            string reason = null;
            if (!IsFixtureValid(fixtureType, ref reason))
            {
                fixture.RunState = RunState.NotRunnable;
                fixture.IgnoreReason = reason;
            }
        }

        /// <summary>
        /// Resolve TestCaseBuilders from NUnit Core Extensions
        /// </summary>
        /// <returns></returns>
        private ITestCaseBuilder2 GetTestBuilders()
        {
            if (_testDecorators == null)
            {
                IExtensionPoint testCaseBuilders = _host.GetExtensionPoint("TestCaseBuilders");
                _testCaseBuilders = (ITestCaseBuilder2)testCaseBuilders;
            }
            return _testCaseBuilders;
        }

        /// <summary>
        /// Resolve TestDecorators from NUnit Core Extensions
        /// </summary>
        /// <returns></returns>
        private ITestDecorator GetTestDecorators()
        {
            if (_testDecorators == null)
            {
                IExtensionPoint testDecorators = _host.GetExtensionPoint("TestDecorators");
                _testDecorators = (ITestDecorator)testDecorators;
            }
            return _testDecorators;
        }


        private static bool IsFixtureValid(Type fixtureType, ref string reason)
        {
            // TODO: Check abstract, proper setup methods, etc
            return true;
        }
        #endregion

        #region instance variables
        private ITestCaseBuilder2 _testCaseBuilders;
        private ITestDecorator _testDecorators;

        //private WebTestCaseBuilder _testCaseBuilder;
        private readonly SeleniumConfiguration _config;
        private readonly IExtensionHost _host;
        #endregion

    }
}
